﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using CodeDocumentationSynchronizer.CodDocSync.EventsListeners;
using CodeDocumentationSynchronizer.CodDocSync.GUI;
using CodeDocumentationSynchronizer.CodDocSync.Helpers;
using EnvDTE;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using VSIXUtilities.DataObjects;
using VSIXUtilities.Helpers;
using VSIXUtilities.Helpers.Extensions;
using VSIXUtilities.Loggers;


namespace CodeDocumentationSynchronizer.CodDocSync
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    [PackageRegistration(UseManagedResourcesOnly = true)]
    // This attribute is used to register the informations needed to show the this package
    // in the Help/About dialog of Visual Studio.
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    // This attribute is needed to let the shell know that this package exposes some menus.
    [ProvideMenuResource("Menus.ctmenu", 1)]
    // This attribute registers a tool window exposed by this package.
    [ProvideToolWindow(typeof(MyToolWindow))]
    [Guid(GuidList.guidCodDocSyncPkgString)]
    [ProvideAutoLoad(UIContextGuids.SolutionHasSingleProject)]
    [ProvideAutoLoad(UIContextGuids.SolutionHasMultipleProjects)]
    public sealed class CodDocSyncPackage : Package
    {
        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public CodDocSyncPackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }

        /// <summary>
        /// This function is called when the user clicks the menu item that shows the 
        /// tool window. See the Initialize method to see how the menu item is associated to 
        /// this function using the OleMenuCommandService service and the MenuCommand class.
        /// </summary>
        private void ShowToolWindow(object sender, EventArgs e)
        {
            // Get the instance number 0 of this tool window. This window is single instance so this instance
            // is actually the only one.
            // The last flag is set to true so that if the tool window does not exists it will be created.
            ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), 0, true);
            if ((null == window) || (null == window.Frame))
            {
                throw new NotSupportedException(Resources.CanNotCreateWindow);
            }
            IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
            ErrorHandler.ThrowOnFailure(windowFrame.Show());
        }

        #region Package Members
        OleMenuCommand OleMenuCommandSyncHierarchyCurrentFile;
        OleMenuCommand OleMenuCommandSyncHierarchyCurrentProject;
        OleMenuCommand OleMenuCommandSyncHierarchyCurrentSolution;

        OleMenuCommand OleMenuCommandSyncHierarchyCurrentMember;

        OleMenuCommand OleMenuCommandSyncHierarchyCustom;

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initilaization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();
            SelectionEvents = new VsSelectionEvents();
            ErrorListLoggerInstance = new ErrorListLogger("CodDocSync");

            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (null != mcs)
            {
                mcs.AddCommand(PkgCmdIDList.cmdidMyToolWindow, ShowToolWindow);

                //mcs.AddCommand(PkgCmdIDList.ButtonOptions, ButtonOptions_Callback);
                OleMenuCommandSyncHierarchyCurrentFile = mcs.AddCommand(PkgCmdIDList.ButtonSyncHierarchyCurrentFile, ButtonSyncHierarchyCurrentFile_Callback, ButtonSyncHierarchyCurrentFile_BeforeQueryStatus);
                OleMenuCommandSyncHierarchyCurrentProject = mcs.AddCommand(PkgCmdIDList.ButtonSyncHierarchyCurrentProject, ButtonSyncHierarchyCurrentProject_Callback, ButtonSyncHierarchyCurrentProject_BeforeQueryStatus);
                OleMenuCommandSyncHierarchyCurrentSolution = mcs.AddCommand(PkgCmdIDList.ButtonSyncHierarchyCurrentSolution, ButtonSyncHierarchyCurrentSolution_Callback, ButtonSyncHierarchyCurrentSolution_BeforeQueryStatus);
                OleMenuCommandSyncHierarchyCurrentMember = mcs.AddCommand(PkgCmdIDList.ButtonSyncCodeCurrentMember, ButtonSyncCodeCurrentMember_Callback, ButtonSyncCodeCurrentMember_BeforeQueryStatus);
                OleMenuCommandSyncHierarchyCustom = mcs.AddCommand(PkgCmdIDList.ButtonSyncCustom, ButtonSyncCustom_Callback, ButtonSyncCustom_BeforeQueryStatus);
            }
            LoadLanguagesSpecifications();
        }
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            SelectionEvents.Dispose();
            ErrorListLoggerInstance.Dispose();
        }
        #endregion

        private void ButtonSyncHierarchyCurrentFile_BeforeQueryStatus(object sender, EventArgs e)
        {
            OleMenuCommandSyncHierarchyCurrentFile.Enabled = !SelectionEvents.Debugging && !SelectionEvents.Building && SelectionEvents.CurrentlyOpened != null && SelectionEvents.CurrentlyOpened.CanBeDocCommented();
        }
        private void ButtonSyncHierarchyCurrentFile_Callback(object sender, EventArgs e)
        {
            if (CurrentProjectItem.FileCodeModel == null)
            {
                IVsHierarchyWrapper ivhw = CurrentProjectItem.ToIVsHierarchyWrapper();
                string FileName = null;
                if (ivhw == null)
                {
                    FileName = CurrentProjectItem.Name;
                }
                else
                {
                    FileName = ivhw.LogicalPath;
                }
                ErrorListLoggerInstance.Log(string.Format("The code in file '{0}' can't be synchronized with the xml documentation becuase it contains errors", FileName), TaskErrorCategory.Error, CurrentProjectItem, true, new EventHandler(delegate(object sender2, EventArgs e2)
                {
                    MessageBox.Show("Try to build the project properly to solve this problem.", "CodDocSync", MessageBoxButton.OK, MessageBoxImage.Information);
                }));
                return;
            }
            new SynchronizationCore(CurrentProjectItem).Dump();
        }
        private void ButtonSyncHierarchyCurrentProject_BeforeQueryStatus(object sender, EventArgs e)
        {
            OleMenuCommandSyncHierarchyCurrentProject.Enabled = (SelectionEvents.SolutionHasSingleProject || SelectionEvents.SolutionHasMultipleProjects) && !SelectionEvents.Debugging && !SelectionEvents.Building && SelectionEvents.CurrentProject != null;
        }
        private void ButtonSyncHierarchyCurrentProject_Callback(object sender, EventArgs e)
        {
            ProcessItem(SelectionEvents.CurrentProject);
        }
        private void ButtonSyncHierarchyCurrentSolution_BeforeQueryStatus(object sender, EventArgs e)
        {
            OleMenuCommandSyncHierarchyCurrentSolution.Enabled = (SelectionEvents.SolutionHasSingleProject || SelectionEvents.SolutionHasMultipleProjects) && !SelectionEvents.Debugging && !SelectionEvents.Building && SelectionEvents.CurrentSolution != null;
        }
        private void ButtonSyncHierarchyCurrentSolution_Callback(object sender, EventArgs e)
        {
            ProcessItem(SelectionEvents.CurrentSolution);
        }
        private void ButtonSyncCodeCurrentMember_BeforeQueryStatus(object sender, EventArgs e)
        {
            OleMenuCommandSyncHierarchyCurrentMember.Enabled = !SelectionEvents.Debugging && !SelectionEvents.Building && SelectionEvents.CurrentlyOpened != null && SelectionEvents.CurrentlyOpened.CanBeDocCommented();
        }
        private void ButtonSyncCodeCurrentMember_Callback(object sender, EventArgs e)
        {
            /*
            EnvDTE.DTE DTEInstance = (DTE)GetService(typeof(SDTE));
            ProjectItem prit = DTEInstance.ActiveDocument.ProjectItem;
            //Microsoft.VisualStudio.OLE.Interop.IServiceProvider serviceProvider;
            //((IVsProject)prit.ContainingProject.ToIVsHierarchy()).GetItemContext(prit.GetItemId(), out serviceProvider);
            //ICodeParser icp = serviceProvider.GetICodeParser();

            CodeCompileUnit ccu = prit.GetCodeCompileUnit();
            //ccu.Namespaces[0].Types[0].Members[0].StartDirective

            CodeTypeMember ctm = ccu.Namespaces[0].Types[0].Members[0];
            string stringUserData = ctm.UserData.GetDataString();
            
            
            
            vsCMPrototype protty = vsCMPrototype.vsCMPrototypeParamNames;
            
            CodeElement[] results = new CodeElementHelper().GetCurrentElements();
            CodeElement ce = results[results.Length - 1];

            CodeFunction2 cf2 = ((CodeFunction2)ce);
            string prot = cf2.get_Prototype((int)protty);
            TextPoint tpStart = cf2.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes);
            TextPoint tpEnd = cf2.GetStartPoint(vsCMPart.vsCMPartNavigate);
            string[] strlines = tpStart.CreateEditPoint().GetLines(tpStart.Line, tpEnd.Line).ToLines();
            
            //string[] strlines = cf2.StartPoint.CreateEditPoint().GetLines(cf2.StartPoint.Line, cf2.StartPoint.Line + 1).ToLines();


            //ctm.CustomAttributes
            
             * 
            return;
            //ICodeParser generator = new CSharpCodeProvider().CreateParser();            
            //var partialMethodExtender = ctm.ToCodeElement().get_Extender("PartialMethod") as ICSPartialMethodExtender
            //ICSPartialMethodExtender
            //VBPartialMethodExtender
            CodeFunction2 aa;
            //aa.get_Prototype(vsCMPrototype.vsCMPrototypeParamNames);

             * 
             * 
             */
            CodeElement[] result = new CodeElementHelper().GetCurrentElements();
            //MessageBox.Show(String.Join(Environment.NewLine, result.Select(CE => CE.Name)));
            WindowSyncCurrentMember WindowSyncCurrentMemberInstance = new WindowSyncCurrentMember(result);
            WindowSyncCurrentMemberInstance.ShowDialog();

        }
        private void ButtonSyncCodeCurrentNameSpace_Callback(object sender, EventArgs e)
        {
            //IVsHierarchyWrapper IVsHierarchyWrapperCurrentProject = SelectionEvents.CurrentProject;
            //if (IVsHierarchyWrapperCurrentProject != null)
            //{
            //    CodeElement ce = GetCurrentNamespace();
            //    SearchForNamespace(IVsHierarchyWrapperCurrentProject, ce);
            //}
        }
        private void ButtonSyncCustom_BeforeQueryStatus(object sender, EventArgs e)
        {
            OleMenuCommandSyncHierarchyCustom.Enabled = (SelectionEvents.SolutionHasSingleProject || SelectionEvents.SolutionHasMultipleProjects) && !SelectionEvents.Debugging && !SelectionEvents.Building && SelectionEvents.CurrentSolution != null;
        }
        private void ButtonSyncCustom_Callback(object sender, EventArgs e)
        {
            DTE ddd = (DTE)ServiceProvider.GlobalProvider.GetService(typeof(SDTE));
            FormFileCodeModelBrowser FFCMB = new FormFileCodeModelBrowser(ddd.ActiveDocument.ProjectItem.FileCodeModel);

            //FormFileCodeModelBrowser FFCMB = new FormFileCodeModelBrowser(SelectionEvents.CurrentlySelected.ToProjectItem().FileCodeModel);

            FFCMB.ShowDialog();
        }

        internal static VsSelectionEvents SelectionEvents
        {
            get;
            private set;
        }
        private ProjectItem CurrentProjectItem
        {
            get
            {
                uint projectItemId;
                IVsHierarchy hierarchy = GetCurrentHierarchy(out projectItemId);

                Microsoft.VisualStudio.OLE.Interop.IServiceProvider serviceProvider;
                ((IVsProject)hierarchy).GetItemContext(projectItemId, out serviceProvider);

                return GetProjectItem(hierarchy, projectItemId);
            }
        }
        private Document CurrentDocument
        {
            get
            {
                return CurrentProjectItem.Document;
            }
        }
        private FileCodeModel CurrentFileCodeModel
        {
            get
            {
                return CurrentProjectItem.FileCodeModel;
            }
        }

        private ErrorListLogger ErrorListLoggerInstance
        {
            get;
            set;
        }


        private ProjectItem GetProjectItem(IVsHierarchy hierarchy, uint ItemID)
        {
            if (hierarchy == null)
                throw new ArgumentNullException("hierarchy");
            Object prjItemObject = null;
            ErrorHandler.ThrowOnFailure(hierarchy.GetProperty(
             ItemID, (int)__VSHPROPID.VSHPROPID_ExtObject, out prjItemObject));

            return prjItemObject as ProjectItem;
        }
        private IVsHierarchy GetCurrentHierarchy(out uint projectItemId)
        {
            IntPtr hierarchyPtr, selectionContainerPtr;

            IVsMultiItemSelect mis;

            IVsMonitorSelection monitorSelection = (IVsMonitorSelection)Package.GetGlobalService(typeof(SVsShellMonitorSelection));

            monitorSelection.GetCurrentSelection(out hierarchyPtr, out projectItemId, out mis, out selectionContainerPtr);

            return Marshal.GetTypedObjectForIUnknown(hierarchyPtr, typeof(IVsHierarchy)) as IVsHierarchy;
        }
        private void ProcessItem(IVsHierarchyWrapper Item)
        {
            if (Item != null)
            {
                using (FormEnableUndo FormEnableUndoInstance = new FormEnableUndo())
                {
                    if (FormEnableUndoInstance.ShowDialog() == System.Windows.Forms.DialogResult.Yes)
                    {
                        ProcessItemRecursively(Item, FormEnableUndoInstance.OpenFiles);
                    }
                    System.Windows.Forms.MessageBox.Show("Done");
                }
            }
        }
        private void ProcessItemRecursively(IVsHierarchyWrapper Item, bool OpenFiles)
        {
            if (Item.CanBeDocCommented())
            {
                //EnvDTE.DTE ddd = (DTE)ServiceProvider.GlobalProvider.GetService(typeof(SDTE));
                //bool aaa = ddd.ItemOperations.IsFileOpen(Item.FilePath);
                ProjectItem ProjectItemInstance = Item.ToProjectItem();
                if (ProjectItemInstance.GetBuildAction() == VSLangProj.prjBuildAction.prjBuildActionCompile)
                {
                    if (ProjectItemInstance.IsOpen)
                    {
                        new SynchronizationCore(ProjectItemInstance).Dump();
                    }
                    else
                    {
                        if (OpenFiles)
                        {
                            ProjectItemInstance.Open().Activate();
                            new SynchronizationCore(ProjectItemInstance).Dump();
                        }
                        else
                        {
                            new SynchronizationCore(ProjectItemInstance).Dump();
                            ProjectItemInstance.Save();
                        }
                    }
                }
            }
            else
            {
                foreach (IVsHierarchyWrapper IVsHierarchyWrapperChild in Item.Children)
                {
                    ProcessItemRecursively(IVsHierarchyWrapperChild, OpenFiles);
                }
            }
        }
        private TextPoint GetCursorTextPoint()
        {
            DTE ddd = (DTE)ServiceProvider.GlobalProvider.GetService(typeof(SDTE));
            TextDocument objTextDocument = default(TextDocument);
            TextPoint objCursorTextPoint = default(TextPoint);
            try
            {
                objTextDocument = (TextDocument)ddd.ActiveDocument.Object();
                objCursorTextPoint = objTextDocument.Selection.ActivePoint;
            }
            catch
            {
            }
            return objCursorTextPoint;
        }
        public void GetCodeElementAtCursor()
        {
            DTE ddd = (DTE)ServiceProvider.GlobalProvider.GetService(typeof(SDTE));
            CodeElement objCodeElement = default(CodeElement);
            TextPoint objCursorTextPoint = default(TextPoint);

            try
            {
                objCursorTextPoint = GetCursorTextPoint();

                if ((objCursorTextPoint != null))
                {
                    // Get the class at the cursor
                    objCodeElement = GetCodeElementAtTextPoint(vsCMElement.vsCMElementClass, ddd.ActiveDocument.ProjectItem.FileCodeModel.CodeElements, objCursorTextPoint);
                }

                if (objCodeElement == null)
                {
                    MessageBox.Show("No class found at the cursor!");
                }
                else
                {
                    MessageBox.Show("Class at the cursor: " + objCodeElement.FullName);
                }

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }

        }
        private CodeElement GetCodeElementAtTextPoint(vsCMElement eRequestedCodeElementKind, CodeElements colCodeElements, TextPoint objTextPoint)
        {
            //EnvDTE.CodeElement objCodeElement = default(EnvDTE.CodeElement);
            CodeElement objResultCodeElement = default(CodeElement);
            CodeElements colCodeElementMembers = default(CodeElements);
            CodeElement objMemberCodeElement = default(CodeElement);

            if ((colCodeElements != null))
            {
                foreach (CodeElement objCodeElement in colCodeElements)
                {
                    if (objCodeElement.StartPoint.GreaterThan(objTextPoint))
                    {
                        // The code element starts beyond the point
                    }
                    else if (objCodeElement.EndPoint.LessThan(objTextPoint))
                    {
                        // The code element ends before the point
                        // The code element contains the point
                    }
                    else
                    {
                        if (objCodeElement.Kind == eRequestedCodeElementKind)
                        {
                            // Found
                            objResultCodeElement = objCodeElement;
                        }
                        // We enter in recursion, just in case there is an inner code element that also 
                        // satisfies the conditions, for example, if we are searching a namespace or a class
                        colCodeElementMembers = GetCodeElementMembers(objCodeElement);
                        objMemberCodeElement = GetCodeElementAtTextPoint(eRequestedCodeElementKind, colCodeElementMembers, objTextPoint);
                        if ((objMemberCodeElement != null))
                        {
                            // A nested code element also satisfies the conditions
                            objResultCodeElement = objMemberCodeElement;
                        }
                        break; // TODO: might not be correct. Was : Exit For
                    }

                }
            }
            return objResultCodeElement;

        }
        private CodeElements GetCodeElementMembers(CodeElement objCodeElement)
        {
            CodeElements colCodeElements = default(CodeElements);
            if (objCodeElement is CodeNamespace)
            {
                colCodeElements = ((CodeNamespace)objCodeElement).Members;
            }
            else if (objCodeElement is CodeType)
            {
                colCodeElements = ((CodeType)objCodeElement).Members;
            }
            else if (objCodeElement is CodeFunction)
            {
                colCodeElements = ((CodeFunction)objCodeElement).Parameters;
            }
            return colCodeElements;
        }
        public void CodeElementFromPointExample2(DTE dte)
        {
            // Before running this example, open a code document from a project
            // and place the insertion point anywhere inside the source code.
            try
            {
                TextSelection sel = (TextSelection)dte.ActiveDocument.Selection;
                TextPoint pnt = (TextPoint)sel.ActivePoint;

                // Discover every code element containing the insertion point.
                FileCodeModel fcm =
                    dte.ActiveDocument.ProjectItem.FileCodeModel;
                string elems = "";
                vsCMElement scopes = 0;

                foreach (vsCMElement scope in Enum.GetValues(scopes.GetType()))
                {
                    CodeElement elem;
                    try
                    {
                        elem = fcm.CodeElementFromPoint(pnt, scope);
                    }
                    catch (Exception)
                    {
                        continue;
                    }
                    if (elem != null)
                    {
                        elems += elem.Name + " (" + scope.ToString() + ")\n";
                    }
                }

                MessageBox.Show("The following elements contain the insertion point:\n\n" + elems);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        public CodeElement[] CodeElementFromPointExample(DTE dte)
        {
            List<CodeElement> Result = new List<CodeElement>();
            try
            {
                TextSelection sel = (TextSelection)dte.ActiveDocument.Selection;
                TextPoint pnt = (TextPoint)sel.ActivePoint;

                FileCodeModel fcm = dte.ActiveDocument.ProjectItem.FileCodeModel;
                foreach (vsCMElement scope in Enum.GetValues(typeof(vsCMElement)))
                {
                    try
                    {
                        Result.Add(fcm.CodeElementFromPoint(pnt, scope));
                    }
                    catch (Exception)
                    {
                        continue;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            return Result.OrderByDescending(CE => CE.StartPoint.Line).ToArray();
        }
        public CodeElement GetCurrentNamespace()
        {
            DTE dte = (DTE)ServiceProvider.GlobalProvider.GetService(typeof(SDTE));
            CodeElement elemelem = null;
            // Before running this example, open a code document from a project
            // and place the insertion point anywhere inside the source code.
            try
            {
                TextSelection sel = (TextSelection)dte.ActiveDocument.Selection;
                TextPoint pnt = (TextPoint)sel.ActivePoint;

                // Discover every code element containing the insertion point.
                FileCodeModel fcm = dte.ActiveDocument.ProjectItem.FileCodeModel;
                //vsCMElement scopes = 0;

                elemelem = fcm.CodeElementFromPoint(pnt, vsCMElement.vsCMElementNamespace);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            return elemelem;
        }
        private void LoadLanguagesSpecifications()
        {
            Cache.LoadLanguagesSpecifications();
        }
    }
}
